﻿using System;
using System.Globalization;
using System.Text;

namespace VerwInfo
{
    public static class Utility
    {
        private static readonly char[] AllowedCharacters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .()+&$*:;-/,%?@='""".ToCharArray();

        private static readonly CultureInfo Ci = CultureInfo.InvariantCulture;

        /// <summary>
        /// Convert a Unicode string to a string containing only characters that are allowed in a ClieOp file.
        /// </summary>
        /// <param name="str">A Unicode string.</param>
        /// <returns>A cleaned string according to the allowed characters in the ClieOp specification.</returns>
        public static string ClieOpAscii(string str)
        {
            var sb = new StringBuilder();
            foreach (char ch in str.Normalize(NormalizationForm.FormD))
            {
                if (Array.IndexOf(AllowedCharacters, ch) >= 0)
                {
                    sb.Append(ch);
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// Trim or pad the string to make it fixed length. Also clean the string from characters that are illegal
        /// in the ClieOp specifiaction.
        /// </summary>
        /// <param name="str">The raw string.</param>
        /// <param name="len">The desired string length.</param>
        /// <returns>The cleaned string with a length of `len' characters.</returns>
        public static string ClieOpString(string str, int len)
        {
            return ClieOpAscii(str).PadRight(len).Substring(0, len);
        }

        /// <summary>
        /// Pad the number with `0' chars to give it the required fixed field length.
        /// </summary>
        /// <param name="num">The raw number.</param>
        /// <param name="len">The desired number's length.</param>
        /// <returns>The padded number as a fixed-length string.</returns>
        public static string ClieOpNum(string num, int len)
        {
            string format = string.Format("{{0:{0}}}", new string('0', len));
            string s = string.Format(Ci, format, long.Parse(num, Ci));
            return s.Length > len ? s.Substring(s.Length - len, len) : s;
        }

        /// <summary>
        /// Return a date as a string in the ClieOp date specification.
        /// </summary>
        /// <param name="date">A DateTime object.</param>
        /// <returns>The date as a six-digit string.</returns>
        public static long ClieOpDatum(DateTime date)
        {
            return long.Parse(string.Format(Ci, "{0:ddMMyy}", date), Ci);
        }

        /// <summary>
        /// Return the current date as a string in the ClieOp date specification.
        /// </summary>
        /// <returns>The current date as a six-digit string.</returns>
        public static long ClieOpDatum()
        {
            return ClieOpDatum(DateTime.Today);
        }

        /// <summary>
        /// Converts the ClieOp date to a dateTime object.
        /// </summary>
        /// <param name="clieOpDate">The ClieOp date.</param>
        public static DateTime ClieOpDatumToDateTime(int clieOpDate)
        {
            return new DateTime(2000 + (clieOpDate % 100), (clieOpDate / 100) % 100, clieOpDate / 10000);
        }

        /// <summary>
        /// Check a nine-digit bank account.
        /// </summary>
        /// <param name="account">The account number to check.</param>
        /// <returns>The check status of the account number.</returns>
        public static BankAccountCheckStatus IsValidBankaccount(long account)
        {
            string accountString = account.ToString(Ci);
            if (accountString.Length != 9)
            {
                return BankAccountCheckStatus.NotABankAccount;
            }
            long t = 0;
            for (int i = 0; i < 9; i++)
            {
                t += int.Parse(accountString.Substring(i, 1), Ci) * (9 - i);
            }
            return t % 11 == 0 ? BankAccountCheckStatus.Valid : BankAccountCheckStatus.InValid;
        }

        /// <summary>
        /// Return true if the account number is a Postbank account number (less than nine digits) or if it is a valid nine-digit
        /// bank account. (If the number is more than eight digits, this function uses `IsValidBankaccount()' internally.)
        /// </summary>
        /// <param name="account">The account number to check.</param>
        /// <returns>True if the number can be used in ClieOp, otherwise false.</returns>
        public static bool IsValidAccount(long account)
        {
            return account.ToString(Ci).Length < 9 || IsValidBankaccount(account) == BankAccountCheckStatus.Valid;
        }
    }

    /// <summary>
    /// Possible results from the `IsValidBankAccount' check.
    /// </summary>
    public enum BankAccountCheckStatus
    {
        Valid,
        InValid,
        NotABankAccount
    }

    /// <summary>
    /// ClieOp can be used for two types of transactions. (They can't be mixed.)
    /// </summary>
    public enum TransactionFileType
    {
        DirectDebit,
        BusinessPayments
    }
}